home *** CD-ROM | disk | FTP | other *** search
Korn shell script | 1997-08-26 | 13.9 KB | 455 lines |
- #!/bin/ksh
- # @(#) frcp.ksh 2.8 97/06/06
- # 92/06/29 john h. dubois iii (john@armory.com)
- # 92/10/14 Cleaned up, improved, added -d and -r options
- # 92/11/11 Made work with a dest of '.'
- # 93/07/09 Added -l and -n options, & login as anonymous if no .netrc entry
- # 93/11/14 Use either passwd or password in .netrc, since ftp does.
- # 94/10/05 Changed local machine name to that returned by hostname instead
- # of uname, since it's used in anonymous ftp password and some sites
- # want a FQDN.
- # 94/10/16 Added -i to ftp args. Do not lcd when mgetting.
- # 95/11/02 Strip trailing . from hostname used as password for anonymous ftp;
- # some ftpds don't like that.
- # 96/03/08 If -D is given and dest is remote, create dirs on remote machine
- # 96/05/22 Added support for ftp URLs.
- # 96/06/11 Added sw options.
- # 96/07/17 Remove any trailing : and 23 from machine name given in FTP URLs.
- # 96/12/18 Added gtDL options.
- # 96/12/24 Added px options.
- # 97/04/01 Make coprocess output go to stderr.
- # 97/06/06 Rewrote .netrc parsing to make it more like ftp
-
- # frcp: ftp front end with rcp-like syntax.
- # Note: requires machine names given to be listed with
- # user and password in .netrc; if not, login "anonymous" is used.
-
- alias istrue="test 0 -ne"
- alias isfalse="test 0 -eq"
-
- # For each (machine,path) given, put the filename in filename[n]
- # and the machine it is on in machine[n].
- # (machine,path) may be given as machine:path or ftp://machine/path or
- # ftp://machine:23/path or ftp://machine:/path
- function SplitNames {
- typeset file sourceMach=$LocalMach paths
- typeset -i i=1
-
- set -A paths -- "$@"
- [[ -n "$gethost" && "${paths[$#-1]}" != *:* ]] && sourceMach=$gethost
-
- unset filename[*] machine[*]
- for file; do
- case "$file" in
- @(ftp|FTP)://*)
- file=${file#???://}
- machine[i]=${file%%?(:?(23))/*}
- filename[i]=${file#*/}
- ;;
- *:*)
- machine[i]=${file%%:*}
- filename[i]=${file#*:}
- ;;
- *)
- [ i -lt $# ] && machine[i]=$sourceMach || machine[i]=$LocalMach
- filename[i]=$file
- ;;
- esac
- let i+=1
- done
- }
-
- function verboseprint {
- print -u1 "$*"
- print -u2 "$*"
- }
-
- function MakeDir {
- OFS=$IFS
- typeset IFS=/ dir component
-
- [[ $1 != /* ]] && dir=.
- set -- $1
- IFS=$OFS
- for component; do
- dir=$dir/$component
- if [ ! -d "$dir" ]; then
- if mkdir "$dir"; then :; else
- print -u2 "Could not make directory $dir."
- return 1
- fi
- fi
- done
- return 0
- }
-
- function MakeRemoteDir {
- OFS=$IFS
- typeset IFS=/ dir component
-
- [[ $1 != /* ]] && dir=.
- set -- $1
- IFS=$OFS
- for component; do
- dir=$dir/$component
- # Optimization: only create dir if we didn't last time
- [[ "$dir" = "$lastdir" || "$lastdir" = "$dir/"* ]] ||
- verboseprint mkdir $dir
- done
- lastdir=$dir
- return 0
- }
-
- # CopyFiles: issue ftp(TC) commands to copy files.
- # Usage: CopyFiles [sourcemachine:]sourcepath ... [destmachine:]destpath
- # An alternate way of specifying machine:path, for the sake of copied & pasted
- # URLs, is: ftp://machine/path
- # Global vars:
- # Uses LocalMach (should be name of local machine), defDest
- # Sets global arrs machine[]/filename[]
- function CopyFiles {
- unset machine[*] filename[*]
- [ -n "$defDest" ] && set -- "$@" "$defDest"
- SplitNames "$@" # split names into filename[1..n] and machine[1..n]
- typeset DestMach=${machine[$#]} # Machine to copy files to
- typeset DestPath=${filename[$#]} # Destination file/dir
- unset machine[$#] filename[$#]
- [ -z "$DestPath" ] && DestPath=. # dest was given as machine:
-
- $debug && print -ru2 -- \
- "
- Source machines: ${machine[*]}
- Source files: ${filename[*]}
- Dest machine: $DestMach
- Dest path: $DestPath"
- # Try to determine if destination should be a directory
- # so that it can be forced to be a directory.
- [[ $DestPath != */ && # don't add / if it already ends in /
- ( $# -gt 2 || # if more than two args given, last must be a dir
- # If dest in on local machine, we can check whether it is a directory
- $DestMach = $LocalMach && -d $DestPath ||
- # If dest ends with . or .., it is a directory
- # If we are appending source path, dest must be a directory
- $DestPath = ?(*/)@(.|..) ) ]] || $AddSourcePath && DestPath=$DestPath/
-
- # If one of the above tests made us think dest is a directory,
- # but it isn't, complain
- if [[ ( "$DestPath" = */ ) &&
- ( "$DestMach" = $LocalMach ) && ! -d "$DestPath" ]];
- then
- print -u2 "Destination is not a directory."
- exit
- fi
- DoCopy "$DestMach" "$DestPath"
- }
-
- # Usage: OpenMachine machine-name
- # Emits login sequence or doesn't, depending on .netrc file and global
- # variables anon and noanon
- function OpenMachine {
- typeset machine=$1 netrc=$HOME/.netrc user= password=
-
- [ "$machine" = "$lastmachine" ] && return 0
- lastmachine=$machine
-
- # .netrc parsing is done as closely to ftp as possible.
- # ftp processes .netrc as a stream of tokens separated by whitespace or ","
- # Unfortunately standard awk can only have one record separator character,
- # so must use FS, and has a limit on the number of fields per record, so
- # must still deal with one line at a time even though newline is the same
- # as the other separators as far as ftp is concerned.
- if isfalse anon && [ -r $netrc ]; then
- awk -F'[ \t,]' "-vmachine=$machine" -vdebug=$debug '
- BEGIN {
- split("machine,default,login,password,passwd,account,macdef",e,",")
- for (i = 1; i in e; i++)
- names[e[i]]
- debug = debug == "true"
- if (debug)
- printf "Reading .netrc; debugging is on\n" > "/dev/stderr"
- }
- function gotmach(m) {
- if (gotMachName) { # if there was a previous entry, process it
- if (debug)
- printf "Ending entry for machine <%s>\n",machName > "/dev/stderr"
- if (machName == "" || machName == machine) {
- if (debug)
- printf "Found search machine <%s>\n",machName > "/dev/stderr"
- if ("passwd" in Fields)
- Fields["password"] = Fields["passwd"]
- if ("login" in Fields && "password" in Fields)
- print Fields["login"] " " Fields["password"]
- gotMachName = 0
- exit
- }
- }
- if (debug)
- printf "Starting entry for machine <%s>\n",m > "/dev/stderr"
- split("",Fields)
- gotMachName = 1
- machName = m
- }
- function dotok(tok) {
- if (debug)
- printf "got token <%s>\n",tok > "/dev/stderr"
- if (name == "") {
- if (debug)
- printf "Directive is <%s>\n",tok > "/dev/stderr"
- if (tok == "default")
- gotmach("")
- else if (tok in names)
- name = tok
- else if (debug)
- printf "Unrecognized directive <%s>\n",tok > "/dev/stderr"
- }
- else if (name == "macdef") {
- if (debug)
- printf "In macro definition <%s>\n",tok > "/dev/stderr"
- continue
- }
- else {
- if (debug)
- printf "Directive <%s> gets value <%s>\n",name,tok > "/dev/stderr"
- if (name == "machine")
- gotmach(tok)
- else
- Fields[name] = tok
- name = ""
- }
- }
- {
- if (debug)
- printf "Processing line: %s\n",$0 > "/dev/stderr"
- if (name == "macdef") {
- if ($0 ~ /^[ \t\n,]*$/) # end of macdef
- name = ""
- if (debug)
- printf "Macro text: %s\n",$0 > "/dev/stderr"
- }
- else
- for (i = 1; i <= NF; i++)
- dotok($i)
- }
- END {
- if (gotMachName)
- gotmach("END OF FILE")
- if (debug)
- printf "Finished processing .netrc\n" > "/dev/stderr"
- }
- ' $netrc | read user password
- fi
- if [ -z "$password" ]; then
- if istrue noanon; then
- print -u2 "No .netrc entry for machine $machine"
- exit 1
- fi
- user=anonymous
- password=$USER@${LocalMach%%.}
- fi
- verboseprint open $machine
- print -u2 "user $user *******"
- print "user $user $password"
- }
-
- # Usage: DoCopy destination-machine destination-path
- # Copies the files in global arrs machine[]/filename[] to the given dest
- # Global vars:
- # Uses machine[], filename[], LocalMach, check
- function DoCopy {
- typeset DestMach=$1
- typeset DestPath=$2
- typeset OpenMach # Machine that connection is currently open to
- typeset SourceMach SourceFile
- typeset -i i=1
- typeset FileName
-
- while [ i -le ${#machine[*]} ]; do
- istrue check && verboseprint "runique"
-
- SourceMach=${machine[i]}
- SourceFile=${filename[i]}
-
- DestFile=$DestPath
- if $PreCWD; then
- [[ "$DestFile" != /* ]] && DestFile=$PWD/$DestFile
- [[ "$SourceFile" != /* ]] && SourceFile=$PWD/$SourceFile
- fi
- # if DestPath is a dir, add source filename to it without source path
- if [[ "$DestFile" = */ ]]; then
- $AddSourcePath && DestFile=$DestFile$SourceFile ||
- DestFile=$DestFile${SourceFile##*/}
- fi
-
- if [ $SourceMach = $LocalMach ]; then
- if [ $DestMach != "$OpenMach" ]; then
- OpenMachine $DestMach
- OpenMach=$DestMach
- fi
- if istrue createdirs; then
- MakeRemoteDir "${DestFile%/*}"
- fi
- verboseprint put $SourceFile $DestFile
- elif [ $DestMach = $LocalMach ]; then
- if istrue check && [ -f "$DestFile" ]; then
- print -u2 "$DestFile already exists."
- continue
- fi
- # If destination is on local machine,
- # the dest will be a full dir/filename
- if istrue createdirs; then
- MakeDir "${DestFile%/*}" || continue
- fi
- if [ $SourceMach != "$OpenMach" ]; then
- OpenMachine $SourceMach
- OpenMach=$SourceMach
- fi
- # If source filename has wildcards ([, ], *, ?) do an mget
- if [[ "$SourceFile" = *[][*?]* ]]; then
- # verboseprint lcd "$DestFile"
- verboseprint mget "$SourceFile"
- # verboseprint lcd $PWD
- else
- verboseprint get "$SourceFile" "$DestFile"
- dests[destct]=$DestFile
- let destct+=1
- fi
- else
- print -u2 "Neither source machine \"$SourceMach\" "\
- "nor destination machine \"$DestMach\" is local."
- fi
- let i+=1
- done
- }
-
- # This has to be something that actually reads all of its input, so that
- # the output process won't get SIGPIPE
- function discard {
- typeset line
- while read line; do
- :
- done
- }
-
- ### Start of main program
- nam=${0##*/}
- AddSourcePath=false
- PreCWD=false
- do_l=false
- pairs=false
- debug=false
-
- typeset -i check=0 createdirs=0 readinput=0 anon=0 noanon=0 destct=0
- typeset gethost= ftp="ftp -in"
-
- while getopts :cdflnrswg:htD:Lpx Option
- do
- case "$Option" in
- h)
- print -r -- \
- "$nam: do ftp transfers using rcp-style parameters.
- Usage: $nam [-cdtfhlnrswL] [-g<host>] [-D<dest>] <source> [<source> ...] <dest>
- At least one of <source> and <destpath> must be the local system.
- A remote filename is given as machinename:filename or ftp://hostname/filename
- If remote filenames contain wildcards, they will be globbed on the remote
- machine. Make sure they are quoted when $nam is invoked.
- If the invoking user's .netrc file (see ftp(TC)) contains an entry for the
- remote system (or a \"default\" entry) with a login and password supplied, $nam
- will log in using the given login and password. If not, $nam will login in as
- user anonymous and with the user@localsystem as the password.
- Files are transferred into the local directory or into the named remote
- directory regardless of the path to the source file.
- Options:
- -c: check: do not overwrite files.
- -d: create directories as needed.
- -D<dest>: All non-option arguments are taken to be source files and are copied
- to destination <dest>. This lets the destination be given as the first
- argument intead of the last, and also lets input lines given for the -r
- option consist of source filenames only.
- -p: Each pair of filenames given on the command line is taken to be a source
- and destination pair. For example, \"$nam a b c d e f\" is equivalent to
- invoking $nam three time separately, with a b, c d, and e f as arguments.
- -t: Tell what commands would be issued to ftp without actually doing it.
- -f: force: overwrite files (default).
- -g<host>: If the destination is on the local host (does not contain a ':'), all
- source files that do not have a host name specified (do not contain a ':')
- are assumed to exist on <host>.
- -h: print this help.
- -l: fail if there is no entry with login and password for the remote system,
- instead of logging in as anonymous.
- -L: After transferring allfiles, do an 'ls -l' on any destination files that
- are on the local host. Files copied using wildcards are skipped.
- -n: log in as anonymous even if there is an entry for the remote system in
- the user's .netrc file.
- -r: read lists of source files and destination from the standard input, one
- set per line, and copy files accordingly. If -D is given, input lines may
- consist of single filenames.
- -s: Append the entire pathname of the source file to the destination for
- each file copied. If no destination path is given, '.' is used, so the
- destination will become relative to the current directory (if the
- destination is the local) or the ftp login directory (if the destination
- is remote).
- -w: Prepend the current working directory to any of the source files and
- destination that do not begin with a '/'."
- exit 0;;
- g) gethost=$OPTARG;;
- t) ftp=discard;;
- c) check=1;;
- d) createdirs=1;;
- D)
- defDest=$OPTARG
- if [ -z "$defDest" ]; then
- print -u2 -- "$nam: Value given with -D must be non-null."
- exit 1
- fi
- ;;
- f) check=0;;
- l) noanon=1;;
- L) do_l=true;;
- p) pairs=true;;
- n) anon=1;;
- r) readinput=1;;
- s) AddSourcePath=true;;
- w) PreCWD=true;;
- x) debug=true;;
- :)
- print -r -u2 -- \
- "$nam: Option '$OPTARG' requires a value. Use -h for help."
- exit 1
- ;;
- \?) echo "$OPTARG: invalid option."; exit 1;;
- esac
- done
-
- shift $((OPTIND-1))
- if isfalse readinput && [ $# -lt 2 -a -z "$defDest" ]; then
- print -u2 "$nam: Not enough arguments. Use -h for help."
- exit 1
- fi
-
- if $pairs && [ $(($#%2)) -ne 0 ]; then
- print -ru2 -- "Must give an even number of filenames with -p."
- exit 1
- fi
-
- LocalMach=`hostname`
-
- # In order to be able to reference dests[] and destct at the end of the
- # program, we must keep CopyFiles in the context of the parent process.
- # Doing a simple pipe into $ftp puts CopyFiles in a separate process, so
- # instead start it as a coprocess and then write into the coprocess pipe.
- $ftp >&2 |&
- if istrue readinput; then
- while read line; do
- CopyFiles $line
- done
- elif $pairs; then
- while [ $# -gt 0 ]; do
- CopyFiles "$1" "$2"
- shift 2
- done
- else
- CopyFiles "$@"
- fi >&p
- wait # for ftp to finish
- $do_l && [ destct -gt 0 ] && l -- "${dests[@]}"
-